﻿// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.ExtendedReflection.Metadata;
using Microsoft.ExtendedReflection.Monitoring;
using Microsoft.ExtendedReflection.Utilities;
using Microsoft.ExtendedReflection.Utilities.Safe;
using Microsoft.ExtendedReflection.Utilities.Safe.Diagnostics;
using System.Collections;
using DUCover.Core;

namespace DUCover
{
    /// <summary>
    /// For each thread of the monitored application, 
    /// an instance of this class is created by the <see cref="Tracer"/>.
    /// </summary>
    sealed class ThreadTracer :
        ThreadExecutionMonitorEmpty
    {
        Stack<Method> methodStack = new Stack<Method>();

        public ThreadTracer(int threadId)
            : base(threadId)
        { }

        #region tracing
        private void trace(string formatString, params object[] args)
        {
            trace(SafeString.Format(formatString, args));
        }

        static readonly object traceLock = new object();
        private void trace(string message)
        {
            lock (traceLock)
            {
                Console.Write("{0}[{1}] ", this.indentString, this.ThreadId);
                Console.WriteLine(message);
            }
        }

        int level;
        string indentString;
        private void indent(int delta)
        {
            this.level += delta;
            if (this.level < 0) this.level = 0;
            this.indentString = StringHelper.GetSpaces(this.level);
        }
        #endregion

        #region Callbacks that indicate major control-flow events
        public override bool EnterMethod(Method method)
        {
            this.trace("<enter> {0}", method.FullName);
            this.indent(+1);

            if (this.methodStack.Count == 0)
            {
                //TODO: Extend this to multiple reference assemblies
                var assembly = method.Definition.Module.Assembly;                
                DUCoverMain.Initialize(assembly);
            }

            this.methodStack.Push(method);
            return true; // 'true' indicates that we want callbacks for the argument values
        }

        public override void Argument<T>(int index, T value)
        {
            this.trace("arg{0}: {1}", index, value);
        }

        public override void ArgumentObject(int index, object value)
        {
            this.trace("argobject{0}: {1}", index, value);
        }

        public override void ArgumentByRef<T>(int index, ref T value)
        {
            this.trace("argref{0}: {1}", index, value);
        }

        public override void ReturnArgument<T>(int index, T value)
        {
            if (index < 0) // result argument
                this.trace("return: {0}", value);
            else
                this.trace("rarg{0}: {1}", index, value);
        }

        public override void ReturnArgumentObject(int index, object value)
        {
            if (index < 0) // result argument
                this.trace("return: {0}", value);
            else
                this.trace("refobject{0}: {1}", index, value);
        }

        public override void ReturnArgumentByRef<T>(int index, ref T value)
        {
            if (index < 0) // result argument
                this.trace("return: {0}", value);
            else
                this.trace("refarg{0}: {1}", index, value);
        }

        public override void UnwindMethod(object exception)
        {
            this.trace("unwind: {0}", exception.GetType());
        }

        public override void LeaveMethod()
        {
            this.indent(-1);
            this.trace("<leave>");
            this.methodStack.Pop();

            if (this.methodStack.Count == 0)
            {
                DUCoverMain.Terminate();
            }
        }
        #endregion
    }
}
